{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Metaprogramming - Application 2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There's another pattern we can implement using metaprogramming - Singletons."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you read online, you'll see that singleton objects are controversial in Python. \n",
    "\n",
    "I'm not going to get into a debate on this, other than to say I do not use singleton objects, not because I have deep thoughts about it (or even shallow ones for that matter), but rather because I have never had a need for them."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "However, the question often comes up, so here it is - the metaclass way of implementing the singleton pattern.\n",
    "\n",
    "Whether you think you should use it or not, is entirely up to you!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have seen singleton objects - objects such as `None`, `True` or `False` for example."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No matter where we create them in our code, they always refer to the **same** object."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can recover the type used to create `None` objects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 361,
   "metadata": {},
   "outputs": [],
   "source": [
    "NoneType = type(None)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now we can create multiple instances of that type:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 362,
   "metadata": {},
   "outputs": [],
   "source": [
    "n1 = NoneType()\n",
    "n2 = NoneType()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 363,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(4466448280, 4466448280)"
      ]
     },
     "execution_count": 363,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "id(n1), id(n2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see, any instance of `NoneType` is actually the **same** object."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The same holds true for booleans:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 364,
   "metadata": {},
   "outputs": [],
   "source": [
    "b1 = bool([])\n",
    "b2 = bool(\"\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 365,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(4466348224, 4466348224)"
      ]
     },
     "execution_count": 365,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "id(b1), id(b2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These are all examples of singleton objects. Now matter how we create them, we always end up with a reference to the same instance."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There is no built-in mechanism to Python for singleton objects, so we have to do it ourselves."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The basic idea is this:\n",
    "\n",
    "When an instance of the class is being created (but **before** the instance is actually created), check if an instance has already been created, in which case return that instance, otherwise, create a new instance and store that instance reference somewhere so we can recover it the next time an instance is requested."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We could do it entirely in the class itself, without any metaclasses, using the `__new__` method.\n",
    "\n",
    "We can start with this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Hundred:\n",
    "    def __new__(cls):\n",
    "        new_instance = super().__new__(cls)\n",
    "        setattr(new_instance, 'name', 'hundred')\n",
    "        setattr(new_instance, 'value', 100)\n",
    "        return new_instance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "h1 = Hundred()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'name': 'hundred', 'value': 100}"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vars(h1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But of course, this is not a singleton object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "h2 = Hundred()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h1 is h2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, let's fix this to make it a singleton:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Hundred:\n",
    "    _existing_instance = None  # a class attribute!\n",
    "    \n",
    "    def __new__(cls):\n",
    "        if not cls._existing_instance:\n",
    "            print('creating new instance...')\n",
    "            new_instance = super().__new__(cls)\n",
    "            setattr(new_instance, 'name', 'hundred')\n",
    "            setattr(new_instance, 'value', 100)\n",
    "            cls._existing_instance = new_instance\n",
    "        else:\n",
    "            print('instance exists already, using that one...')\n",
    "        return cls._existing_instance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "creating new instance...\n"
     ]
    }
   ],
   "source": [
    "h1 = Hundred()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "instance exists already, using that one...\n"
     ]
    }
   ],
   "source": [
    "h2 = Hundred()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h1 is h2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And there you are, we have a singleton object."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So this works, but if you need to have multiple of these singleton objects, the code will just become repetitive."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Metaclasses to the rescue!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remember what we are trying to do:\n",
    "\n",
    "If we create two instances of our class `Hundred` we expect the same instance back."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But how do we create an instance of a class - we **call** it, so `Hundred()`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Which `__call__` method is that? It is not the one in the `Hundred` class, that would make **instances** of `Hundred` callable, it is the `__call__` method in the **metaclass**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, we need to override the `__call__` in our metaclass."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Singleton(type):\n",
    "    def __call__(cls, *args, **kwargs):\n",
    "        print(f'Request received to create an instance of class: {cls}...')\n",
    "        return super().__call__(*args, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Hundred(metaclass=Singleton):\n",
    "    value = 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.Hundred'>...\n"
     ]
    }
   ],
   "source": [
    "h = Hundred()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "100"
      ]
     },
     "execution_count": 87,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OK, that works, but now we need to make it into a singleton instance."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We have to be careful here. Initially we had used the class itself (`Hundred`) to store, as a class variable, whether an instance had already been created. \n",
    "\n",
    "And here we could try to do the same thing. \n",
    "\n",
    "We could store the instance as a class variable in the class of the instance being created\n",
    "\n",
    "That's actually quite simple, since the class is received as the first argument of the `__call__` method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Singleton(type):\n",
    "    def __call__(cls, *args, **kwargs):\n",
    "        print(f'Request received to create an instance of class: {cls}...')\n",
    "        if getattr(cls, 'existing_instance', None) is None:\n",
    "            print('Creating instance for the first time...')\n",
    "            setattr(cls, 'existing_instance', super().__call__(*args, **kwargs))\n",
    "        else:\n",
    "            print('Using existing instance...')\n",
    "        return getattr(cls, 'existing_instance')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Hundred(metaclass=Singleton):\n",
    "    value = 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.Hundred'>...\n",
      "Creating instance fior the first time...\n"
     ]
    }
   ],
   "source": [
    "h1 = Hundred()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.Hundred'>...\n",
      "Using existing instance...\n"
     ]
    }
   ],
   "source": [
    "h2 = Hundred()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, 100, 100)"
      ]
     },
     "execution_count": 108,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h1 is h2, h1.value, h2.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So that seems to work just fine. Let's create another singleton class and see if things still work."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Thousand(metaclass=Singleton):\n",
    "    value = 1000"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.Thousand'>...\n",
      "Creating instance fior the first time...\n"
     ]
    }
   ],
   "source": [
    "t1 = Thousand()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.Thousand'>...\n",
      "Using existing instance...\n"
     ]
    }
   ],
   "source": [
    "t2 = Thousand()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, 100, 100)"
      ]
     },
     "execution_count": 109,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h1 is h2, h1.value, h2.value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, 1000, 1000)"
      ]
     },
     "execution_count": 110,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t1 is t2, t1.value, t2.value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(False, False)"
      ]
     },
     "execution_count": 111,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h1 is t1, h2 is t2"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So far so good."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally let's make sure everything works with **inheritance** too - if we inherit from a Singleton class, that subclass should also be a singleton."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "metadata": {},
   "outputs": [],
   "source": [
    "class HundredFold(Hundred):\n",
    "    value = 100 * 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.HundredFold'>...\n",
      "Using existing instance...\n"
     ]
    }
   ],
   "source": [
    "hf1 = HundredFold()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Whaaat? Using existing instance? But this is the first time we created it!!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The problem is this: How are we checking if an instance has already been created?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We did this:\n",
    "```if getattr(cls, 'existing_instance')```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But since `HundredFold` inherits from `Hundred`, it also inherited the class attribute `existing_instance`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This means we have to be a bit more careful in our metaclass, we need to see if we have an instance of the **specific** class already created - and we cannot rely on storing a class attribute in the classes themselves since that breaks the pattern when subclassing."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So, instead, we are going to store the class, and the instance of that class, in a dictionary **in the metaclass** itself, and use that dictionary to lookup the existing instance (if any) for a specific class."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 127,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Singleton(type):\n",
    "    instances = {}\n",
    "    \n",
    "    def __call__(cls, *args, **kwargs):\n",
    "        print(f'Request received to create an instance of class: {cls}...')\n",
    "        existing_instance = Singleton.instances.get(cls, None)\n",
    "        if existing_instance is None:\n",
    "            print('Creating instance for the first time...')\n",
    "            existing_instance = super().__call__(*args, **kwargs)\n",
    "            Singleton.instances[cls] = existing_instance\n",
    "        else:\n",
    "            print('Using existing instance...')\n",
    "        return existing_instance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 128,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Hundred(metaclass=Singleton):\n",
    "    value = 100\n",
    "    \n",
    "class Thousand(metaclass=Singleton):\n",
    "    value = 1000\n",
    "    \n",
    "class HundredFold(Hundred):\n",
    "    value = 100 * 100"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 129,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.Hundred'>...\n",
      "Creating instance for the first time...\n",
      "Request received to create an instance of class: <class '__main__.Hundred'>...\n",
      "Using existing instance...\n"
     ]
    }
   ],
   "source": [
    "h1 = Hundred()\n",
    "h2 = Hundred()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 130,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.Thousand'>...\n",
      "Creating instance for the first time...\n",
      "Request received to create an instance of class: <class '__main__.Thousand'>...\n",
      "Using existing instance...\n"
     ]
    }
   ],
   "source": [
    "t1 = Thousand()\n",
    "t2 = Thousand()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 131,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Request received to create an instance of class: <class '__main__.HundredFold'>...\n",
      "Creating instance for the first time...\n",
      "Request received to create an instance of class: <class '__main__.HundredFold'>...\n",
      "Using existing instance...\n"
     ]
    }
   ],
   "source": [
    "hf1 = HundredFold()\n",
    "hf2 = HundredFold()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 132,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(True, True, True)"
      ]
     },
     "execution_count": 132,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h1 is h2, t1 is t2, hf1 is hf2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(100, 100, 1000, 1000, 10000, 10000)"
      ]
     },
     "execution_count": 133,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h1.value, h2.value, t1.value, t2.value, hf1.value, hf2.value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And just to make sure :-)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 135,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h1 is hf1"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
